﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Diagnostics;

namespace Samcrypt
{


    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public AppModel model { get; private set; }

        public MainWindow()
        {

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);


            model = new AppModel();

            DataContext = model;

            this.InitializeComponent();

            pageTabsListBox.ItemContainerGenerator.ItemsChanged += new System.Windows.Controls.Primitives.ItemsChangedEventHandler(ItemContainerGenerator_ItemsChanged);
            pageTabsListBox.PreviewMouseWheel += new MouseWheelEventHandler(pageTabsListBox_PreviewMouseWheel);
            pageTabsListBox.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("Order", System.ComponentModel.ListSortDirection.Ascending));


            model.PropertyChanged += new PropertyChangedEventHandler(CurDocument_PropertyChanged);
            newDocumentHelper();

            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);


            //start_tap_pulling_test();

        }

        #region Tap Pulling (testing)

        // Issues
        //  - When pulling a tab that aready has a window visable, need to wait untill outside of
        //    windows boundry before hiding or moving window. Currently, window gets hidden imedeatly
        //    as it would if we reentered the main windows boundry (need to distinguis reentry from
        //    already there -- err, exit vs entry)
        //  - Because we are having the page-tab listbax control capture the mosue, it
        //    is inapropreatly getting and handling mouse over events sometimes. Consider
        //    making this conditional or finding a better control to have capture the mouse
        //  - If the windows are manualy closed, they cant be pulled out again. Eather closing needs
        //    to be detected and the window removed from the list of page windows, or the closing
        //    event needs to be captured and the window hiddent instred of closed.
        //  


        void start_tap_pulling_test()
        {
            pageTabsListBox.MouseMove += new MouseEventHandler(pageTabsListBox_MouseMove);
            pageTabsListBox.MouseUp += new MouseButtonEventHandler(pageTabsListBox_MouseUp);
        }

        enum DownState { Down, FirstMove, Moving, Up }

        DownState downState;

        List<PageWindow> pageWindows = new List<PageWindow>();
        PageWindow pageWindow;

        Page mouseDownPage;

        private void myBorder_MouseDown(object sender, MouseButtonEventArgs e)
        {

            FrameworkElement source = e.Source as FrameworkElement;
            ContentControl contentControl = source.TemplatedParent as ContentControl;
            Page page = contentControl.Content as Page;

            if (page != null)
            {
                mouseDownPage = page;
                downState = DownState.Down;
            }

        }

        void pageTabsListBox_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (pageWindow == null)
                return;

            if (pageWindow.Visibility == Visibility.Visible)
                pageWindows.Add(pageWindow);


            downState = DownState.Up;
            
            pageWindow = null;
        }


        void pageTabsListBox_MouseMove(object sender, MouseEventArgs e)
        {
            
            if (Mouse.LeftButton == MouseButtonState.Pressed)
            {
                if (downState == DownState.Down)
                {

                    Page page = mouseDownPage;
                    PageWindow newPageWin = null;

                    var items = from p in pageWindows
                                where p.DataContext == page
                                select p;

                    foreach (PageWindow p in items)
                    {
                        newPageWin = p;
                        break;
                    }

                    if (newPageWin == null)
                        newPageWin = new PageWindow();

                    newPageWin.Owner = this;
                    newPageWin.DataContext = page;

                    pageWindow = newPageWin;

                    Mouse.Capture(pageTabsListBox);

                    mouseDownPage = null;
                    downState = DownState.FirstMove;

                }
                else if (downState == DownState.FirstMove || downState == DownState.Moving)
                {
                    Point p = MouseUtilities.GetMousePosition(this);

                    Console.WriteLine(p);
                    if (this.Width < p.X || p.X < 0 ||
                        this.Height < p.Y || p.Y < 0)
                    {

                        if (pageWindow.Visibility != Visibility.Visible)
                        {
                            pageWindow.Show();
                        }


                        Point screenPoint = this.PointToScreen(p);

                        pageWindow.Left = screenPoint.X;
                        pageWindow.Top = screenPoint.Y;


                    }
                    else
                    {

                        pageWindow.Hide();
                    }

                    downState = DownState.Moving;
                }
            }
        }


        #endregion





        Window1 windows1;
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {

#if DEBUG
            if (windows1 == null)
            {
                windows1 = new Window1();
                windows1.SetOwner(this, model);
            }
            windows1.Show();
#endif
        }



        #region Page view event hacks

        void CurDocument_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Document")
            {
                // Hookup to default view
                ICollectionView view = CollectionViewSource.GetDefaultView(model.Document.Pages);
                view.CurrentChanging += new CurrentChangingEventHandler(view_CurrentChanging);
            }
        }
        
        void view_CurrentChanging(object sender, CurrentChangingEventArgs e)
        {
            // current item of pages view is changeing, do pre-page-change work
            onPreviewSelectedPageChange((ListCollectionView)sender);
        }

        private void pageTabsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // cuurent page has changed, do post page change work  
            // [ hack: this is done via the listbox change (rather than the view) because we want to do work after the UI reflects the change ]
            onSelectedPageChanged((ListBox)sender);
        }

        #endregion

        #region Current page change / update page binging source

        void onPreviewSelectedPageChange(ListCollectionView view)
        {
            updatePageBindingSource();

            savePageState(docBodyTextBox, (Page)view.CurrentItem); // save state of current selected item
        }

        void onSelectedPageChanged(ListBox listBox)
        {
            if (listBox.SelectedItem != null)
                loadPageState(docBodyTextBox, (Page)listBox.SelectedItem);

            docBodyTextBox.Focus();
        }

        void updatePageBindingSource()
        {
            BindingExpression be = docBodyTextBox.GetBindingExpression(TextBox.TextProperty);
            be.UpdateSource();

        }

        #endregion

        #region Saving and loading page state

        void savePageState(TextBox textBox, Page p)
        {
            if (p == null) return;

            p.VM.verticalOffset = textBox.VerticalOffset;
            p.VM.horizontalOffset = textBox.HorizontalOffset;

            p.VM.selectionStart = textBox.SelectionStart;
            p.VM.selectionLength = textBox.SelectionLength;

            p.VM.actualHeight = textBox.ActualHeight;
            p.VM.actualWidth = textBox.ActualWidth;

        }

        void loadPageState(TextBox textBox, Page p)
        {
            if (p == null) return;

            textBox.SelectionStart = p.VM.selectionStart;
            textBox.SelectionLength = p.VM.selectionLength;

            if (Math.Abs(textBox.ActualHeight - p.VM.actualHeight) <= 5 &&
                Math.Abs(textBox.ActualWidth - p.VM.actualWidth) <= 5)
            {
                textBox.ScrollToVerticalOffset(p.VM.verticalOffset);
                textBox.ScrollToHorizontalOffset(p.VM.horizontalOffset);
            }

        }

        #endregion

        #region Page-tabs ListBox

        // Flag first tiem and ensure at least one item is selected
        void ItemContainerGenerator_ItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
        {
            if (pageTabsListBox.Items.Count == 0)
                return;

            if (pageTabsListBox.SelectedIndex < 0)
                pageTabsListBox.SelectedIndex = 0;

            // Flag first item
            foreach (Page p in pageTabsListBox.Items)
            {
                p.VM.IsFirst = false;
            }
            ((Page)pageTabsListBox.Items[0]).VM.IsFirst = true;
        }
        

        // changes mouse wheel behavor to select difrent pages
        void pageTabsListBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            e.Handled = true;

            if (e.Delta < 0)
            {
                if (pageTabsListBox.SelectedIndex < pageTabsListBox.Items.Count - 1)
                    pageTabsListBox.SelectedIndex++;
            }
            else
            {
                if (pageTabsListBox.SelectedIndex > 0)
                    pageTabsListBox.SelectedIndex--;
            }

            pageTabsListBox.ScrollIntoView(pageTabsListBox.SelectedItem);

        }

        public void NewPage()
        {

            Document doc = model.Document;

#if DEBUG
            if (doc.Pages.Count > 0)
            {
                // temp
                Tester.Run();
            }
#endif

            int pageNumber = doc.Pages.Count > 0 ? doc.Pages[doc.Pages.Count - 1].Order + 1 : 1;
            Page p = new Page() { Order = pageNumber };
            doc.Pages.Insert(doc.Pages.Count, p);
            p.SetNew();

            if (pageTabsListBox.Items.Count > 0)
            { // HACK: buggy listbox behavior if these lines arnt excluded in an empty list case
                pageTabsListBox.SelectedItem = p;
                pageTabsListBox.ScrollIntoView(pageTabsListBox.SelectedItem);
            }

            textBox2.Focus();
            textBox2.SelectAll();

        }
        public void DeletePage()
        {
            int curIndex = pageTabsListBox.SelectedIndex;

            model.Document.Pages.Remove((Page)pageTabsListBox.SelectedItem);

            if (model.Document.Pages.Count > 0)
            {
                if (pageTabsListBox.Items.Count - 1 >= curIndex)
                    pageTabsListBox.SelectedIndex = curIndex;

                else if (pageTabsListBox.Items.Count > 0)
                    pageTabsListBox.SelectedIndex = curIndex - 1;
            }
            else
            {
                NewPage();
            }


        }

        #endregion



        private void docBodyTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {


        }





        #region Commands


        void CmdExecuted(object target, ExecutedRoutedEventArgs e)
        {
            RoutedCommand command = (RoutedCommand)e.Command;

            if (command == ApplicationCommands.New)
                newDocumentHelper();

            else if (command == SamcryptCommands.NewPage)
                NewPage();
            else if (command == SamcryptCommands.DeletePage)
                DeletePage();

            else if (command == ApplicationCommands.Open)
                openHelper(DocSource.File);
            else if (command == SamcryptCommands.OpenFromClipboard)
                openHelper(DocSource.Clipboard);

            else if (command == ApplicationCommands.Save)
                SaveHelper(DocSource.File, !model.SavedCopyExists);
            else if (command == SamcryptCommands.SaveToClipboard)
                SaveHelper(DocSource.Clipboard, !model.SavedCopyExists);

            else if (command == ApplicationCommands.SaveAs)
                SaveHelper(DocSource.File, true);
            else
                MessageBox.Show("The " + command.Name + " command has been invoked on target object " + ((FrameworkElement)target).Name);


        }

        void CanExecuteTrue(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        #endregion




        #region MISC Support

        private void newDocumentHelper()
        {
            if (!handleDierty())
                return;

            model.NewDocument();
            NewPage();

            passwordBox.Password = "";
        }

        private bool handleDierty()
        {
            if (model.Document == null ||
                model.Document.EditState == EditState.Clean ||
                model.Document.EditState == EditState.New)
                return true;

            MessageBoxResult result = MessageBox.Show("Changes to " + model.Name + " will be lost.\nSave current document first?", "", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);

            if (result == MessageBoxResult.Cancel)
                return false;

            if (result == MessageBoxResult.Yes)
                return SaveHelper(DocSource.File, !model.SavedCopyExists);

            return true;
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            e.Cancel = !handleDierty();
        }

        #endregion


        #region Open / Save

        

        private bool openHelper(DocSource docSource)
        {
            if (!handleDierty())
                return false;

            switch (docSource)
            {
                case DocSource.File:
                    string path = fileOpenDialog();
                    if (path == null) 
                        return false;

                    model.Path = path;
                    break;

                case DocSource.Clipboard:
                    break;
                default:
                    throw new Exception("Unkown DocSource");
            }
            
            bool result = model.Load(passwordBox.SecurePassword, docSource);

            if (!result)
                MessageBox.Show("Failed Loading File");

            return result;
        }



        private bool SaveHelper(DocSource docSource, bool saveAs)
        {
            updatePageBindingSource();

            switch (docSource)
            {
                case DocSource.File:
                    if (saveAs)
                    {
                        string path = fileSaveDialog(model.Path);
                        if (path == null)
                            return false;

                        model.Path = path;
                    }
                    break;

                case DocSource.Clipboard:
                    break;
                default:
                    throw new Exception("Unkown DocSource");
            }

            bool result = model.Store(passwordBox.SecurePassword, docSource);

            if (!result)
                MessageBox.Show("Failed Saving File");

            return result;
        }

        const string FileDialogFilter = "Samcrypt Documents (.scrypt)|*.scrypt|Text Documents (.txt)|*.txt|All Files|*.*";

        private string fileOpenDialog()
        {
            // Configure open file dialog box
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

            dlg.InitialDirectory = System.IO.Path.GetDirectoryName(model.Path);
            dlg.FilterIndex = 0;
            dlg.Filter = FileDialogFilter; // Filter files by extension

            // Show open file dialog box
            Nullable<bool> result = dlg.ShowDialog();

            // Process open file dialog box results
            if (result == true)
            {
                // Open document
                return dlg.FileName;
            }

            return null;

        }

        private string fileSaveDialog(string path)
        {
            // Configure open file dialog box
            Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();

            dlg.FileName = System.IO.Path.GetDirectoryName(path) + "\\" + System.IO.Path.GetFileNameWithoutExtension(path);
            dlg.FilterIndex = 0;
            dlg.Filter = FileDialogFilter; // Filter files by extension

            // Show open file dialog box
            Nullable<bool> result = dlg.ShowDialog();

            // Process open file dialog box results
            if (result == true)
            {
                // Open document
                return dlg.FileName;
            }

            return null;

        }

        #endregion



        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {

            MessageBoxResult result = MessageBox.Show("Samcrypt has stoped working.\nWould you like to copy the error to clipbaord?", "", MessageBoxButton.YesNo, MessageBoxImage.Error);


            if (result == MessageBoxResult.No)
                LogException((Exception)e.ExceptionObject, false);

            if (result == MessageBoxResult.Yes)
                LogException((Exception)e.ExceptionObject, true);


        }

        public string LogException(Exception ex, bool copyToClipbaord)
        {
            string logText;
            string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Samcrypt_log.txt";

            StringBuilder sb = new StringBuilder(2000);
            BuildExceptionString(ex, sb);

            logText = sb.ToString();

            if (copyToClipbaord)
                Clipboard.SetText(logText);

            using (StreamWriter streamWriter = new StreamWriter(path))
            {
                streamWriter.Write(sb.ToString());
            }

            return logText;
        }


        public void BuildExceptionString(Exception ex, StringBuilder sb)
        {
            sb.AppendLine(string.Format("Message:     {0}", ex.Message));
            sb.AppendLine(string.Format("Source:      {0}", ex.Source));
            sb.AppendLine(string.Format("TargetSite:  {0}", ex.TargetSite));
            sb.AppendLine("StackTrace:     ");
            sb.AppendLine(ex.StackTrace);

            if (ex.InnerException != null)
            {
                sb.AppendLine();
                sb.AppendLine("-----------------------------");
                sb.AppendLine("  InnerException");
                sb.AppendLine();

                BuildExceptionString(ex.InnerException, sb);
            }
        }













    }

    public static class SamcryptCommands
    {
        private static object sync = new object();

        private static RoutedUICommand _openFromClipboard = null;
        private static RoutedUICommand _saveToClipboard = null;

        private static RoutedUICommand _newPage = null;
        private static RoutedUICommand _deletePage = null;

        public static RoutedUICommand NewPage
        {
            get
            {
                if (_newPage == null)
                {
                    lock (sync)
                    {
                        if (_newPage == null)
                        {
                            _newPage = new RoutedUICommand("New Page", "NewPage", typeof(SamcryptCommands));
                        }
                    }
                }
                return _newPage;
            }
        }

        public static RoutedUICommand DeletePage
        {
            get
            {
                if (_deletePage == null)
                {
                    lock (sync)
                    {
                        if (_deletePage == null)
                        {
                            _deletePage = new RoutedUICommand("Delete Page", "DeletePage", typeof(SamcryptCommands));
                        }
                    }
                }
                return _deletePage;
            }
        }

        public static RoutedUICommand OpenFromClipboard
        {
            get
            {
                if (_openFromClipboard == null)
                {
                    lock (sync)
                    {
                        if (_openFromClipboard == null)
                        {
                            _openFromClipboard = new RoutedUICommand("Open From Clipboard", "OpenFromClipboard", typeof(SamcryptCommands));
                        }
                    }
                }
                return _openFromClipboard;
            }
        }

        public static RoutedUICommand SaveToClipboard
        {
            get
            {
                if (_saveToClipboard == null)
                {
                    lock (sync)
                    {
                        if (_saveToClipboard == null)
                        {
                            _saveToClipboard = new RoutedUICommand("Save To Clipboard", "SaveToClipboard", typeof(SamcryptCommands));
                        }
                    }
                }
                return _saveToClipboard;
            }
        }

    }

    public enum DocSource { File, Clipboard }


    #region Value Converters

    [ValueConversion(typeof(object), typeof(string))]
    public class FormattingConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string formatString = parameter as string;
            if (formatString != null)
            {
                return string.Format(culture, formatString, value);
            }
            else
            {
                return value.ToString();
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // we don't intend this to ever be called
            return null;
        }
    }



    #endregion


    // Adorners must subclass the abstract base class Adorner.
    public class SimpleCircleAdorner : Adorner
    {
        // Be sure to call the base class constructor.
        public SimpleCircleAdorner(UIElement adornedElement)
            : base(adornedElement)
        {
        }

        // A common way to implement an adorner's rendering behavior is to override the OnRender
        // method, which is called by the layout system as part of a rendering pass.
        protected override void OnRender(DrawingContext drawingContext)
        {
            Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);

            // Some arbitrary drawing implements.
            SolidColorBrush renderBrush = new SolidColorBrush(Colors.Green);
            renderBrush.Opacity = 0.2;
            Pen renderPen = new Pen(new SolidColorBrush(Colors.Navy), 1.5);
            double renderRadius = 5.0;

            // Draw a circle at each corner.
            drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopLeft, renderRadius, renderRadius);
            drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopRight, renderRadius, renderRadius);
            drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomLeft, renderRadius, renderRadius);
            drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomRight, renderRadius, renderRadius);
        }
    }

    
}
